/*
 * Copyright 2011 es-planning Inc.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package jp.arctos.ursus.extension.net;

import java.io.IOException;
import java.nio.channels.ClosedChannelException;
import java.nio.channels.SelectableChannel;
import java.nio.channels.SelectionKey;
import java.nio.channels.Selector;
import java.util.Iterator;
import java.util.Set;
import java.util.concurrent.atomic.AtomicBoolean;

import jp.arctos.ursus.thread.ThreadManager;
import jp.arctos.ursus.thread.RealtimeThreadParameters;

/**
 * Class to handle access to the selector and control of asynchronous thread manager.
 * 
 */
class SelectorHandler implements Runnable {

	/** Selector. */
    Selector selector;

    /** Asynchronous thread manager. */
    Thread thread;

    /** Closed state. */
    AtomicBoolean isClosed = new AtomicBoolean(false);

    /** Associated service. */
    Service service;

    /**
     * Constructor.
     * 
     * @param service Service to associate
     */
    SelectorHandler(Service service) {
        this.service = service;
    }

    /**
     * Prepare for asynchronous control using the selector.
     * 
     * @param parameters Real-time thread parameters
     * @throws IOException
     */
    void open(RealtimeThreadParameters parameters) throws IOException {
        selector = Selector.open();
        thread = ThreadManager.createThread(parameters, this);
    }

    /**
     * Finish asynchronous control using the selector.
     * 
     * <p>
     * Finishing procedures are done asynchronously.
     * </p>
     */
    void close() {
        isClosed.getAndSet(true);
        selector.wakeup();
    }

    /**
     * Determine if finishing procedures have already been called.
     * 
     * @return "true" if being called, "false" if not being called
     */
    boolean isClosed() {
        return isClosed.get();
    }

    /**
     * Begin asynchronous control.
     * 
     */
    void start() {
        thread.start();
    }

    /**
     * Asynchronous control procedures.
     * 
     * <p>
     * Distribute results of selection made to associated services.
     * </p>
     * 
     */
    public void run() {
        while(!isClosed()) {
            try {
                int keyNum = selector.select();

                if (!isClosed() && keyNum > 0) {
                    Set<SelectionKey> readyKeys = selector.selectedKeys();
                    Iterator<SelectionKey> iterator = readyKeys.iterator();

                    while (!isClosed() && iterator.hasNext()) {
                        SelectionKey key = iterator.next();
                        iterator.remove();

                        try {
                            if (key.isValid()) {
                                if (key.isAcceptable()) {
                                    service.onAcceptable(key);
                                }

                                if (key.isConnectable()) {
                                    service.onConnectable(key);
                                }

                                if (key.isReadable()) {
                                    service.onReadable(key);
                                }

                                if (key.isWritable()) {
                                    service.onWritable(key);
                                }
                            }
                        } catch (Exception e) {
                            // do nothing
                        }
                    }
                }
            } catch (Exception e) {
                service.onSelectionError(e);
            }
        }

        try {
            selector.close();
        } catch (IOException e) {
            // do nothing
        }
    }

    /**
     * Register specified channel to the selector.
     * 
     * @param channel Channel
     * @param ops Set of applicable keys
     * @param object Attachment
     * @throws ClosedChannelException
     */
    void register(SelectableChannel channel, int ops, Object object) throws ClosedChannelException {
        if (!isClosed()) {
            channel.register(selector, ops, object);
        }
    }

    /**
     * Add to specified channel applicable key set.
     * 
     * @param channel Channel
     * @param ops Applicable set to be added 
     */
    void addInterestOps(SelectableChannel channel, int ops) {
        if (!isClosed()) {
            SelectionKey key = channel.keyFor(selector);
            if (key != null) {
                key.interestOps(key.interestOps() | ops);
            }
        }
    }

    /**
     * Remove from specified channel applicable key set.
     * 
     * @param channel Channel
     * @param ops Applicable set to be removed
     */
    void removeInterestOps(SelectableChannel channel, int ops) {
        if (!isClosed()) {
            SelectionKey key = channel.keyFor(selector);
            if (key != null) {
                key.interestOps(key.interestOps() & ~ops);
            }
        }
    }

    /**
     * Configure specified channel applicable key set.
     * 
     * @param channel Channel
     * @param ops Applicable set to be configured 
     */
    void setInterestOps(SelectableChannel channel, int ops) {
        if (!isClosed()) {
            SelectionKey key = channel.keyFor(selector);
            if (key != null) {
                key.interestOps(ops);
            }
        }
    }

    /**
     * Remove registration to selector of specified channel.
     * 
     * @param channel Channel
     */
    void cancel(SelectableChannel channel) {
        if (!isClosed()) {
            SelectionKey key = channel.keyFor(selector);
            if (key != null) {
                key.cancel();
            }
        }
    }

    /**
     * Immediately finish the selector operation.
     * 
     */
    void wakeup() {
        if (!isClosed()) {
            selector.wakeup();
        }
    }
}
